#region MIT Licence
/*
 * Copyright (C) 2014 PdfCreative
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom 
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace PdfCreative.Pdf.Writer.Fonts.Type1
{
    /// <summary>
    /// The Type1FontOutlineFile class represents a font outlines file. It is used to retrieve the plain-text and encrypted-text
    /// lengths of the outline file associated with a Type 1 font. This class is used by the Writer to embed Type 1 fonts.
    /// </summary>
    internal class Type1FontOutlineFile
    {
        // Private fields
        // ==============
        #region Private fields
        private long _plainTextLength;          // The length, in bytes, of the plain-text part of the file
        private long _encryptedLength;          // The length, in bytes, of the encrypted part of the file
        #endregion



        // Public properties
        // =================
        #region Public properties
        /// <summary>
        /// Retrieves the length of the plain-text part of the Type 1 font outlines file.
        /// </summary>
        public long PlainTextLength
        {
            get { return _plainTextLength; }
        }

        /// <summary>
        /// Retrieves the length of the encrypted part of the Type 1 font outlines file.
        /// </summary>
        public long EncryptedLength
        {
            get { return _encryptedLength; }
        }
        #endregion



        // Public methods
        // ==============
        #region Public methods
        /// <summary>
        /// Parses the font file.
        /// </summary>
        /// <param name="filename">The name of the file to parse</param>
        /// <exception cref="Type1FontParseException">An error occurred while parsing the font file. See the
        /// exception's InnerException for more details, where available.</exception>
        public void Parse(string filename)
        {
            if (null == filename) throw new ArgumentNullException("filename");

            Parse(File.OpenRead(filename));
        }

        /// <summary>
        /// Parses the data contained in the stream. The stream must support seeking and data will be read from
        /// the start of the stream.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <exception cref="Type1FontParseException">An error occurred while parsing the font file. See the
        /// exception's InnerException for more details, where available.</exception>
        public void Parse(Stream stream)
        {
            if (null == stream) throw new ArgumentNullException("stream");

            ParseFontStream(stream);
        }
        #endregion



        // Private implementation
        // ======================
        #region Reading font data
        /// <summary>
        /// Parses the Type 1 data contained in the stream. 
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        private void ParseFontStream(Stream stream)
        {
            try
            {
                // Rewind the stream.
                stream.Seek(0, SeekOrigin.Begin);

                // Read the plain text part
                _plainTextLength = ReadPlainText(stream);

                // Read the encrypted part
                if (_plainTextLength > 0) _encryptedLength = ReadEncrypted(stream) - _plainTextLength;

                if (_plainTextLength <= 0 || _encryptedLength <= 0)
                    throw new Type1FontParseException("The font file could not be parsed");
            }
            catch (IOException ex)
            {
                throw new Type1FontParseException("An I/O error occurred while reading the font file", ex);
            }
        } // ParseFontStream()

        /// <summary>
        /// Reads the plain-text part of the Type 1 font outline file until the marker that indicates the
        /// start of the encrypted part.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <returns>The index in the stream of the end of the plain-text part.</returns>
        private long ReadPlainText(Stream stream)
        {
            Debug.Assert(null != stream);

            int current;
            bool finished = false;
            long finger = 0;
            int state = 0;
            StringBuilder token = new StringBuilder();

            // We read from the stream, byte by byte, until we find a 'eexec' token.
            // Because we are not interested in the contents of the file we discern between whitespace and
            // non-whitespace and consider tokens to be sequences of non-whitespace characters, divided by
            // sequences of whitespace characters.
            while (false==finished)
            {
                current = stream.ReadByte();
                if (current >= 0)
                {
                    switch (state)
                    {
                        case 0:
                            // In the whitespace state we ignore whitespace. If we encounter a non-whitespace
                            // character we start a new token.
                            if ('\t' != current && '\r' != current && '\n' != current && ' ' != current)
                            {
                                state = 1;
                                token.Append((char)current);
                            }
                            break;

                        case 1:
                            // In the token state we build up the current token from non-whitespace characters.
                            // If we encounter a whitespace character then we will have completely read our
                            // token.
                            if ('\t' == current || '\r' == current || '\n' == current || ' ' == current)
                            {
                                // If the token is 'eexec' then we're done.
                                state = 0;
                                if (5 == token.Length && "eexec" == token.ToString())
                                    finished = true;
                                else
                                    token = new StringBuilder();
                            }
                            else token.Append((char)current);
                            break;
                    }

                    // next character
                    finger++;

                }
                else
                {
                    // No more bytes are available. We did not find the marker that ends the plain-text portion.
                    finger = -1;
                    finished = true;
                }

            }

            // Return the marker position.
            return finger;
        } // ReadPlainText()

        /// <summary>
        /// Reads the encrypted part of the Type 1 font outline file until the start of the fixed part.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <returns>The index in the stream of the end of the encrypted part.</returns>
        private long ReadEncrypted(Stream stream)
        {
            Debug.Assert(null != stream);

            int current;
            bool finished = false;
            long startOfZeroes = stream.Position;
            int zeroes = 0;
            StringBuilder token = new StringBuilder();

            // We read from the stream, byte by byte, until we have read 512 consecutive zeroes.
            // Zeroes may be interspersed with whitespace but no non-whitespace.
            while (false == finished)
            {
                current = stream.ReadByte();
                if (current >= 0)
                {
                    if ('\t' != current && '\r' != current && '\n' != current && ' ' != current)
                    {
                        // We found non-whitespace. If it's a zero, we increase the zero count.
                        // If it's not a zero then we will have to read zeroes all over again.
                        // Once we have 512 zeroes, we're done.
                        // We remember the position of the first zero so that we can calculate the length
                        // of the encrypted part of the stream.
                        if('0'==current)
                        {
                            if (0 == zeroes) startOfZeroes = stream.Position;
                            zeroes++;
                            if(512==zeroes) finished = true;
                        }
                        else{
                            zeroes = 0;
                        }
                    }
                }
                else
                {
                    // No more bytes are available. We did not find the marker that ends the plain-text portion.
                    startOfZeroes = 0;
                    finished = true;
                }

            }

            // Return the marker position.
            return startOfZeroes-1;

        } // ReadEncrypted()
        #endregion

    } // Type1FontOutlineFile class
}
